// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Generic;
using System.Linq;
using SnapObjects.Commons;

namespace SnapObjects.Data
{
    internal class CompositeModelAccessor<TModel> : ICompositeModelAccessor<TModel>
    {
        private readonly IModelAccessor<TModel> _modelAccessor;
        private readonly IDictionary<string, IModelFactory> _embeddedModelFactorys;

        public CompositeModelAccessor()
        {
            this.ModelFactory = ModelFactoryFactory.Create<TModel>();
            _modelAccessor = this.ModelFactory.GetAccessor();

            this.NonGenericModelFactory = new NoGenericModelFactory<TModel>(this.ModelFactory);

            var modelMeta = ModelMetaManager.GetModelMeta(typeof(TModel));

            _embeddedModelFactorys = new Dictionary<string, IModelFactory>(
                                        StringComparer.OrdinalIgnoreCase);

            foreach (var embedded in modelMeta.EmbeddedProperties)
            {
                if (embedded.EmbeddedType.IsModel())
                {
                    var embeddedModelFactory =
                        ModelFactoryFactory.Create(embedded.EmbeddedType);

                    _embeddedModelFactorys.Add(embedded.Name, embeddedModelFactory);
                }
            }
        }

        public IModelMeta ModelMeta => _modelAccessor.ModelMeta;

        public IModelFactory<TModel> ModelFactory { get; }

        public IModelFactory NonGenericModelFactory { get; }

        public IModelFactory GetEmbeddedModelFactory(string name)
        {
            _embeddedModelFactorys.TryGetValue(name,
                                        out var embeddedFactory);

            return embeddedFactory;
        }

        public object GetValue(TModel model, string name)
        {
            return _modelAccessor.GetValue(model, name);
        }

        public void SetValue(TModel model, string name, object value)
        {
            _modelAccessor.SetValue(model, name, value);
        }

        public void SetEmbeddedValue(
            IEmbeddedStateTracker embeddedStateTracker,
            ModelSelector modelSelector,
            object model,
            IPocoProperty property,
            string name,
            object value)
        {
            if (!property.IsEmbedded)
            {
                return;
            }

            var propertyValue = this.GetValue((TModel)model, property.Name);

            if (propertyValue == null)
            {
                return;
            }

            _embeddedModelFactorys.TryGetValue(property.Name,
                                        out var embeddedFactory);

            var embeddedAccessor = embeddedFactory.GetAccessor();

            if (property.IsContainerType)
            {
                var embeddedModels = ((IEnumerable<object>)propertyValue).Cast<object>();
                foreach (var embeddedModel in embeddedModels)
                {
                    //embeddedAccessor.SetValue(embeddedModel, name, value);
                    this.SetEmbeddedValue(embeddedStateTracker, modelSelector, embeddedAccessor,
                                        embeddedModel, name, value);
                }
            }
            else
            {
                //embeddedAccessor.SetValue(propertyValue, name, value);
                this.SetEmbeddedValue(embeddedStateTracker, modelSelector, embeddedAccessor,
                                    propertyValue, name, value);
            }
        }

        private void SetEmbeddedValue(
            IEmbeddedStateTracker embeddedStateTracker,
            ModelSelector modelSelector,
            IModelAccessor embeddedAccessor,
            object model,
            string name,
            object value)
        {
            if (embeddedStateTracker != null &&
                modelSelector != ModelSelector.All)
            {
                var modelState = embeddedStateTracker.GetEmbeddedState(model);

                if ((modelSelector == ModelSelector.ChangedOnly &&
                        (modelState == ModelState.NewModified ||
                            modelState == ModelState.Modified)) ||
                    (modelSelector == ModelSelector.UnchangedOnly &&
                        (modelState == ModelState.New ||
                            modelState == ModelState.NotModified)))
                {
                    embeddedAccessor.SetValue(model, name, value);
                }
            }
            else
            {
                embeddedAccessor.SetValue(model, name, value);
            }
        }

        public object GetValue(object model, IPocoProperty property)
        {
            if (!property.IsEmbedded)
            {
                return null;
            }

            return this.GetValue((TModel)model, property.Name);
        }
    }
}
